using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using Zsa.Emulation.PPU;
using System.Drawing.Imaging;
using System.IO;

namespace Zsa.Emulation.Render.Video
{
    /// <summary>
    /// Renders the graphics
    /// </summary>
    public abstract class GraphicRender :
        RenderDevice<GraphicRenderCapabilities, GraphicRenderSettings, GraphicRender>
    {
        #region Fields
        /// <summary>
        /// Gets the ppu.
        /// </summary>
        private PPU.PictureProcessingUnit _Ppu;
        private ColorPixelSettings _PixelSettings = ColorPixelSettings.Create32BitARGB();

        /// <summary>
        /// Settings for drawing colors on the pixels.
        /// </summary>
        public struct ColorPixelSettings
        {
            /// <summary>
            /// Creates a 32bit argb.
            /// </summary>
            /// <returns></returns>
            public static ColorPixelSettings Create32BitARGB()
            {
                return new ColorPixelSettings(4, 3, 2, 1, 0,
                    0xFF, 0xFF, 0xFF, 0xFF);
            }

            #region Fields
            /// <summary>
            /// The pixel size.
            /// </summary>
            public readonly byte PixelSize;

            /// <summary>
            /// The alpha offset.
            /// </summary>
            public readonly byte Color_A_ByteOffset;

            /// <summary>
            /// The red offset.
            /// </summary>
            public readonly byte Color_R_ByteOffset;

            /// <summary>
            /// The green ofset.
            /// </summary>
            public readonly byte Color_G_ByteOffset;

            /// <summary>
            /// The blue offset.
            /// </summary>
            public readonly byte Color_B_ByteOffset;

            /// <summary>
            /// The alpha offset.
            /// </summary>
            public readonly byte A_Mask;

            // The mask
            public readonly byte R_Mask;

            public readonly byte G_Mask;

            public readonly byte B_Mask;
            #endregion Fields

            /// <summary>
            /// Initializes a new instance of the <see cref="T:ColorPixelSettings"/> class.
            /// </summary>
            /// <param name="pixelSize">Size of the pixel.</param>
            /// <param name="colorAOffset">The color A offset.</param>
            /// <param name="colorROffset">The color R offset.</param>
            /// <param name="colorGOffset">The color G offset.</param>
            /// <param name="colorBOffset">The color B offset.</param>
            /// <param name="colorAMask">The color A mask.</param>
            /// <param name="colorRMask">The color R mask.</param>
            /// <param name="colorGMask">The color G mask.</param>
            /// <param name="colorBMask">The color B mask.</param>
            public ColorPixelSettings(byte pixelSize, byte colorAOffset,
                byte colorROffset,byte colorGOffset,byte colorBOffset,
                byte colorAMask, byte colorRMask, byte colorGMask, byte colorBMask)
            {
                PixelSize = pixelSize;
                Color_A_ByteOffset = colorAOffset;
                Color_R_ByteOffset = colorROffset;
                Color_G_ByteOffset = colorGOffset;
                Color_B_ByteOffset = colorBOffset;

                A_Mask = colorAMask;
                R_Mask = colorRMask;
                G_Mask = colorGMask;
                B_Mask = colorBMask;

            }
        }

        #endregion Fields
        
        #region Constructor
        /// <summary>
        /// Initializes a new instance of the <see cref="T:GraphicRender"/> class.
        /// </summary>
        /// <param name="surfaceAreaControl">The surface area control.</param>
        protected GraphicRender(GraphicRenderSettings settings )
            : base ( settings )
        {
        }
        #endregion Constructor

        #region Methods




        /// <summary>
        /// Handles the Resize event of the SurfaceControl control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
        private void SurfaceControl_Resize(object sender, EventArgs e)
        {
            OnResize();
        }

        /// <summary>
        /// Called when [surface attached].
        /// </summary>
        protected override void OnSurfaceAttached(SurfaceAreaControl value)
        {
            if (_SurfaceControl != null)
            {
                _SurfaceControl.Resize -= new EventHandler(SurfaceControl_Resize);
              //  value.FindForm().Move -= new EventHandler(SurfaceControl_Resize);
            }
            if (value != null)
            {
                value.Resize += new EventHandler(SurfaceControl_Resize);
                //value.FindForm().Move += new EventHandler(SurfaceControl_Resize);
                
            }
            _SurfaceControl = value;
            InitDevice();
        }

        protected override void OnMachineAttached()
        {
            _Ppu = NesHost.Machine.Ppu;
        }

        /// <summary>
        /// Called when [resize].
        /// </summary>
        public virtual void OnResize()
        {
            InitDevice();
        }
        /// <summary>
        /// Updates the render settings.
        /// </summary>
        /// <param name="graphicsSettings">The graphics settings.</param>
        public void UpdateRenderSettings(GraphicRenderSettings graphicsSettings)
        {
            // Check max instances, settings, etc.
            OnUpdateRenderSettings(graphicsSettings);
        }

        /// <summary>
        /// Initialize the graphic render.
        /// </summary>       
        protected abstract void OnUpdateRenderSettings(GraphicRenderSettings renderSettings);

        /// <summary>
        /// Draws the frame on to the screen.
        /// </summary>
        public abstract void DrawScreen();


        #region Draw Patterns Methods

        /// <summary>
        /// Debugs the write table images.
        /// </summary>
        public void DebugSaveBackground()
        {

            Image _Image = CreateNametableImage(_Ppu.PpuMemory.NameAttributeTableOne);

            string _FileName = Config.NesConfigurationManager.Profiles.NameTableImageOutputPath + "\\" +
                Path.GetFileNameWithoutExtension(_NesMachineHost.CurrentCart.FileName)
                + "_0_" + DateTime.Now.Ticks.ToString() + ".bmp";
            _Image.Save(_FileName, ImageFormat.Bmp);

            _Image = CreateNametableImage(_Ppu.PpuMemory.NameAttributeTableTwo);
            _FileName = Config.NesConfigurationManager.Profiles.NameTableImageOutputPath + "\\" +
                Path.GetFileNameWithoutExtension(_NesMachineHost.CurrentCart.FileName)
                + "_1_" + DateTime.Now.Ticks.ToString() + ".bmp";
            _Image.Save(_FileName, ImageFormat.Bmp);

        }



        protected void DrawSprites(IntPtr imgPtr, int stride, byte pixelSize)
        {
            // Get the sprite byte.
            byte[] _SprRam = _Ppu.SpriteRam;
            // The current sprite.
            byte _SpriteIndex = 0;
            // Get the default pattern table.
            PatternTable _PatternTable = _Ppu.SpritePatternTable;
            // Get the color palette.
            PaletteTable _Palette = _Ppu.PpuMemory.SpriteColorPalette;

            SpriteFlags _Flags;
            bool _BigSprites = _Ppu.LargeSprites;
            byte _X, _Y, _ColrAtr;

                        //            _Offset = ((stride * _TileIndexY) * 8);
                        //_ImagePtrPosition = _ImgPointer + _Offset + (8 * _TileIndexX * pixelSize);

            PatternTile _SpriteTile;
            unsafe
            {
                byte* _Scan0, _CurrentSpritePtr;
                NesSprite* sprite;
                fixed (byte* sprPtr = &_SprRam[0])
                {
                    sprite = (NesSprite*)sprPtr;
                    _CurrentSpritePtr = sprPtr;

                    while (_SpriteIndex < 64)
                    {

                        // Get the y.
                        _Y = (byte)(((*(_CurrentSpritePtr)) - 1));
                        // Get the x.
                        _X = (byte)(*(_CurrentSpritePtr + 3));
                        // Get the sprite index from the pattern table.
                        _SpriteIndex = *(_CurrentSpritePtr + 1);
                        // Get the sprite 
                        _ColrAtr = (byte)((*(_CurrentSpritePtr + 2) & 3) << 2);
                        // Get the sprite flags.
                        _Flags = (SpriteFlags)((*(_CurrentSpritePtr + 2) & 0xE0));
                        

                        // Wtf is this for 
                        //if (_X == 0 && _Y == 0xFF)
                        //{
                        //    _CurrentSpritePtr += 4;
                        //    continue;
                        //}
                        // Wtf is this for?
//                        int _TableIndex = _X + (_Y / 32);

                       

                        // Get the start pixel.  right now assuming 
                        // the x,y are tiles and not pixels.
                        _Scan0 = ((byte*)imgPtr.ToPointer()) +
                            ((stride * _Y) * 1) +
                            (1 * _X * pixelSize);
                        
                        // If 8x16 sprite then use sprite then use pattern table 
                        if (_BigSprites)
                        {
                            bool table1000 = (_SpriteIndex & 1) == 1;
                            _SpriteIndex >>= 1;
                            _SpriteIndex *= 2;
                            try
                            {
                                _SpriteTile =
                                    table1000 ? _Ppu.PpuMemory.PatternTableOne[_SpriteIndex] :
                                     _Ppu.PpuMemory.PatternTableTwo[_SpriteIndex];

                                _SpriteTile.Flip(_Flags & (SpriteFlags.FlipHorizontally
                                    | SpriteFlags.FlipVertically));


                                // Draw the sprite.
                                _SpriteTile.DrawTile(
                                   _Scan0, stride, pixelSize, _ColrAtr, _Palette);
                            }
                            catch (Exception excep)
                            {
                                Console.WriteLine(excep);
                            }

                            try
                            {
                                _Scan0 += (stride * 8);

                                _SpriteTile =
                                    table1000 ? _Ppu.PpuMemory.PatternTableOne[(byte)(_SpriteIndex + 1)] :
                                     _Ppu.PpuMemory.PatternTableTwo[(byte)(_SpriteIndex + 1)];

                                _SpriteTile.Flip(_Flags & (SpriteFlags.FlipHorizontally
                                    | SpriteFlags.FlipVertically));


                                // Draw the sprite.
                                _SpriteTile.DrawTile(
                                   _Scan0, stride, pixelSize, _ColrAtr, _Palette);

                            }
                            catch (Exception excep)
                            {
                                Console.WriteLine(excep);
                            }

                        }
                        else
                        {
                            _SpriteTile = _PatternTable[_SpriteIndex];
                            _SpriteTile.Flip(_Flags & (SpriteFlags.FlipHorizontally
                                | SpriteFlags.FlipVertically));
                            _SpriteTile.DrawTile(
                                _Scan0, stride, pixelSize, _ColrAtr, _Palette);
                        }

                        // Move to next sprite.
                        _CurrentSpritePtr += 4;
                        sprite++;
                    }
                }
            }
        }



        [CLSCompliant(false)]
        internal unsafe void DrawTile(PatternTile* _Tile,
            byte* startPixel, int stride, PaletteTable palette,  byte colorAttribute)
        {
            
            // The bit of the pixel to get.  1-64 since using longs.
            int _Adr = 63;
            // The color of the 
            int _ColorIndex;
            // Pointer to start of the current row.
            byte* _Buffer = startPixel;
            // Pointers to the colors.
            byte* _R, _G, _B, _A;
            // The starting byte of the color
            byte* _ColorStart;

            
            // Set the pointers.
           // palette.SetPointers(_Buffer, _R, _G, _B, _A);

            fixed (ColorPixelSettings* _Settings = &_PixelSettings)
            {

                // Loop through the bits.
                for (int _IndexY = 0; _IndexY < PatternTile.TileHeight; _IndexY++)
                {
                    // Get the bit.  Add 7 since the bits for each row are backwards??
                    _Adr = (_IndexY * PatternTile.TileWidth) + 7;
                    // Move to the first row.
                    _Buffer = (startPixel + (_IndexY * stride));
                    // Loop through the bits.
                    for (int _IndexX = 0; _IndexX < PatternTile.TileWidth; _IndexX++)
                    {
                        // Get the index of the tile.
                        _ColorIndex = (int)
                            (((_Tile->_Tile_Clr1 >> _Adr) & 1) |
                            ((_Tile->_Tile_Clr2 >> _Adr) & 1) << 1 | (ulong)colorAttribute);

                        // Get the color from the palette.
                        _ColorIndex = palette.GetColor(_ColorIndex);
                        // Set the pointers for the rgb and alpha.
                        _ColorStart = _Buffer + (_IndexX * _Settings->PixelSize);
                        _B = _ColorStart + _Settings->Color_B_ByteOffset;
                        _G = _ColorStart + _Settings->Color_G_ByteOffset;
                        _R = _ColorStart + _Settings->Color_R_ByteOffset;
                        _A = _ColorStart + _Settings->Color_A_ByteOffset;
                        // Set the color values.

                        *_B = (byte)(_ColorIndex & _Settings->B_Mask);
                        *_G = (byte)((_ColorIndex >> 8) & _Settings->G_Mask);
                        *_R = (byte)((_ColorIndex >> 16) & _Settings->R_Mask);
                        *_A = (byte)((_ColorIndex >> 24) & _Settings->A_Mask);
                        // Move to the previous pixel value since rows are backwards.
                        _Adr--;
                    }
                }
            }
        }




        public Image CreateNametableImage(NameAttributeTable table)
        {
            // Create a rectangle for the image area.
            Rectangle _ImageRectangle =
                new Rectangle(
                    0, 0,
                    256,
                    256
                );
            // Create the bitmap.
            Bitmap _Bitmap = new Bitmap(
                _ImageRectangle.Width,
                _ImageRectangle.Height,
                PixelFormat.Format32bppArgb
                );

            // Lock the bits.
            BitmapData _BitmapData =
                _Bitmap.LockBits(
                    _ImageRectangle,
                    ImageLockMode.ReadWrite,
                    PixelFormat.Format32bppArgb
                );
            // Write the table.
            WriteTable(table, _BitmapData.Scan0, _BitmapData.Stride, 4);
            // Unlock the bits.
            _Bitmap.UnlockBits(_BitmapData);
            // return the bitmap
            return _Bitmap;
        }


        /// <summary>
        /// Writes the current name table to memory.
        /// </summary>
        /// <param name="imgPtr">Pointer to memory location to write the address to..</param>
        /// <param name="stride">The stride of image or row width in bytes..</param>
        /// <param name="pixelSize">The size in bytes of a pixel.</param>
        protected void WriteTable(
            IntPtr imgPtr, int stride, byte pixelSize)
        {
            WriteTable(_Ppu.BackgroundNameTable, imgPtr, stride, pixelSize);
        }

        /// <summary>
        /// Writes the name table to memory.
        /// </summary>
        /// <param name="nameAttributeTable">The name attribute table.</param>
        /// <param name="imgPtr">Pointer to memory location to write the address to..</param>
        /// <param name="stride">The stride of image or row width in bytes..</param>
        /// <param name="pixelSize">The size in bytes of a pixel.</param>
        protected unsafe void WriteTable(NameAttributeTable nameTable,
            IntPtr imgPtr, int stride, byte pixelSize)
        {

            byte* _ImgPointer = (byte*)imgPtr.ToPointer();
            // Get the internal buffer.
            byte[] _NameAttributeTable = nameTable.InternalBuffer;

            // Get the pattern table.
            // byte[] _PatternTable = _Ppu.PpuMemory.PatternTableOne.InternalBuffer;
            PatternTable _PatternTable = _Ppu.BackgroundPatternTable;

            //The palette.
            PaletteTable _Palette = _Ppu.PpuMemory.BackgroundColorPalette;

            // A pattern tile.
            PatternTile _PatternTile;
            // The offsets.
            int _Offset = 0;

            int _SquareIndex = 0;
            byte _AttributeValue;

            unsafe
            {
                // Make a copy of the start position.
                byte* _ImageStartPtr = _ImgPointer;
                byte* _ImagePtrPosition;

                // Fix the attribute table.
                fixed (byte* _AtrPtr = &_NameAttributeTable[0])
                {
                    // Set the attribute ptr to be the 960th byte.
                    byte* _AttributeStartPtr = _AtrPtr + 959;

                    // The name start.
                    byte* _NameStartPtr = _AtrPtr;
                    byte* _CurrentAttribute;
                    

                    for (int _TileIndexY = 0; _TileIndexY < 30; _TileIndexY++)
                    {
                        //
                        _CurrentAttribute = _AttributeStartPtr + (8 * (_TileIndexY));

                        _Offset = ((stride * _TileIndexY) * 8);

                        // If odd square then set the 
                        _SquareIndex = _TileIndexY % 2 != 0 ? 2 : 0;

                        for (int _TileIndexX = 0; _TileIndexX < 32; _TileIndexX++)
                        {
                            _ImagePtrPosition = _ImgPointer + _Offset + (8 * _TileIndexX * pixelSize);

                            if ((_TileIndexX % 2) == 0)
                            {
                                _CurrentAttribute++;
                                _SquareIndex &= 2;
                            }
                            else
                            {
                                _SquareIndex |= 1;
                            }
                            
                            // Get the value
                            switch (_SquareIndex)
                            {
                                case 0:
                                    _AttributeValue = (byte)(((*_CurrentAttribute) & 0x03)<<2);
                                    break;
                                case 1:
                                    _AttributeValue = (byte)(((*_CurrentAttribute) & 0x0C));
                                    break;
                                case 2:
                                    _AttributeValue = (byte)(((*_CurrentAttribute) & 0x30) >> 2);
                                    break;
                                case 3:
                                    _AttributeValue = (byte)(((*_CurrentAttribute) & 0xC0 >> 4));
                                    break;
                                default:
                                    _AttributeValue = 0;
                                    break;
                            }

                            // Attributes broke ?  or is it math in processor?
                            _AttributeValue = 0;



                            _PatternTile = _PatternTable[(int)((byte)(*_NameStartPtr))];
                    
                            PatternTile* _TilePtr = &_PatternTile;
                            DrawTile(_TilePtr, _ImagePtrPosition, stride, _Palette, _AttributeValue);
                  


                           // _PatternTable[(int)((byte)(*_NameStartPtr))].DrawTile(
                           //    _ImagePtrPosition,
                           //    stride,
                           //    pixelSize,
                           //    _AttributeValue,
                           //    _Palette
                           //);
                            // Move to the next tile.
                           _NameStartPtr++;
                        }

           
                    }
                }
            }
        }


        protected unsafe void DrawScanline(IntPtr imgPtr, int stri)
        {
        }

        //protected void DrawBackgroundImage(IntPtr imagePtr, int stride)
        //{

        //}

        //protected unsafe void WriteTableNew(byte* nameTable,
        //    byte* patternPtr, byte* imgPtr, int stride, ushort patternCount)
        //{

        //    byte* _ImgPointer = (byte*)imgPtr.ToPointer();
        //    // Get the internal buffer.
        //    byte[] _NameAttributeTable = nameTable.InternalBuffer;

        //    // Get the pattern table.
        //    // byte[] _PatternTable = _Ppu.PpuMemory.PatternTableOne.InternalBuffer;
        //    PatternTable _PatternTable = _Ppu.BackgroundPatternTable;

        //   // byte[] _PatternBytes = _PatternTable.InternalBuffer;

        //    //The palette.
        //    PaletteTable _Palette = _Ppu.PpuMemory.BackgroundColorPalette;

        //    // A pattern tile.
        //    PatternTile _PatternTile;
        //    // The offsets.
        //    int _Offset = 0;

        //    int _SquareIndex = 0;
        //    byte _AttributeValue;

        //    unsafe
        //    {
        //        // Make a copy of the start position.
        //        byte* _ImageStartPtr = _ImgPointer;
        //        byte* _ImagePtrPosition;
 
        //        // Fix the attribute table.
        //        fixed (byte* _AtrPtr = &_NameAttributeTable[0])
        //        {
        //            fixed (byte* _PatternPtr = &_PatternTable.InternalBuffer)
        //            {

        //                byte* _PatternStartPtr = _PatternPtr + offset;
        //                // Set the attribute ptr to be the 960th byte.
        //                byte* _AttributeStartPtr = _AtrPtr + 959;

        //                // The name start.
        //                byte* _NameStartPtr = _AtrPtr;
        //                byte* _CurrentAttribute;


        //                for (int _TileIndexY = 0; _TileIndexY < 30; _TileIndexY++)
        //                {
        //                    //
        //                    _CurrentAttribute = _AttributeStartPtr + (8 * (_TileIndexY));

        //                    _Offset = ((stride * _TileIndexY) * 8);

        //                    // If odd square then set the 
        //                    _SquareIndex = _TileIndexY % 2 != 0 ? 2 : 0;

        //                    for (int _TileIndexX = 0; _TileIndexX < 32; _TileIndexX++)
        //                    {
        //                        _ImagePtrPosition = _ImgPointer + _Offset + (8 * _TileIndexX * pixelSize);

        //                        if ((_TileIndexX % 2) == 0)
        //                        {
        //                            _CurrentAttribute++;
        //                            _SquareIndex &= 2;
        //                        }
        //                        else
        //                        {
        //                            _SquareIndex |= 1;
        //                        }

        //                        // Get the value
        //                        switch (_SquareIndex)
        //                        {
        //                            case 0:
        //                                _AttributeValue = (byte)(((*_CurrentAttribute) & 0x03) << 2);
        //                                break;
        //                            case 1:
        //                                _AttributeValue = (byte)(((*_CurrentAttribute) & 0x0C));
        //                                break;
        //                            case 2:
        //                                _AttributeValue = (byte)(((*_CurrentAttribute) & 0x30) >> 2);
        //                                break;
        //                            case 3:
        //                                _AttributeValue = (byte)(((*_CurrentAttribute) & 0xC0 >> 4));
        //                                break;
        //                            default:
        //                                _AttributeValue = 0;
        //                                break;
        //                        }

        //                        // Attributes broke ?  or is it math in processor?
        //                        _AttributeValue = 0;



        //                        _PatternTile = _PatternTable[(int)((byte)(*_NameStartPtr))];

        //                        PatternTile* _TilePtr = _NameTablePtr + ((byte)(*_NameStartPtr));
        //                           // & _PatternTile;
        //                        DrawTile(
        //                            _TilePtr, 
        //                            _ImagePtrPosition, stride, _Palette, _AttributeValue);



        //                        // _PatternTable[(int)((byte)(*_NameStartPtr))].DrawTile(
        //                        //    _ImagePtrPosition,
        //                        //    stride,
        //                        //    pixelSize,
        //                        //    _AttributeValue,
        //                        //    _Palette
        //                        //);
        //                        // Move to the next tile.
        //                        _NameStartPtr++;
        //                    }


        //                }
        //            }
                
        //        }
                
        //    }
            
        //}
        #endregion Draw Patterns Methods





        #endregion Methods

        #region Properties



        /// <summary>
        /// Gets the ppu.
        /// </summary>
        /// <value>The ppu.</value>
        internal PPU.PictureProcessingUnit Ppu
        {
            get
            {
                return _Ppu;
            }
            set
            {
                _Ppu = value;
            }
    
        }

        /// <summary>
        /// Gets the parent form.
        /// </summary>
        /// <value>The parent form.</value>
        public Form ParentForm
        {
            get
            {
                return _SurfaceControl.FindForm();
            }
        }


        /// <summary>
        /// Gets the render area.
        /// </summary>
        /// <value>The render area.</value>
        public Rectangle RenderArea
        {
            get
            {
                //return new Rectangle(
                //    _SurfaceControl.Location,
                //    _SurfaceControl.Size
                //    );

                return new Rectangle(
                    new Point(0,0),
                    _SurfaceControl.Size
                    );
            }
        }

        /// <summary>
        /// Gets the width.
        /// </summary>
        /// <value>The width.</value>
        public int Width
        {
            get
            {
                return _SurfaceControl.Width;
            }
        }

        /// <summary>
        /// Gets the height.
        /// </summary>
        /// <value>The height.</value>
        public int Height
        {
            get
            {
                return _SurfaceControl.Height;
            }
        }

        /// <summary>
        /// Gets the size of the tile width.
        /// </summary>
        /// <value>The size of the tile width.</value>
        public int TileWidthSize
        {
            get
            {
                int _TileWidth = _SurfaceControl.Width / 32;
                return _TileWidth;
            }
        }

        /// <summary>
        /// Gets the size of the tile height.
        /// </summary>
        /// <value>The size of the tile height.</value>
        public int TileHeightSize
        {
            get
            {
                int _TileHeight = _SurfaceControl.Height / 30;
                return _TileHeight;
            }
        }

        /// <summary>
        /// Gets the current palette.
        /// </summary>
        /// <value>The current palette.</value>
        public ColorPaletteSettings CurrentPalette
        {
            get
            {
                return DeviceSettings.Palette;
            }
        }

        #endregion Properties



    }
}
